﻿#include "FITKGraphObjectLoadBase.h"

// VTK
#include <vtkPolyDataAlgorithm.h>
#include <vtkPoints.h>
#include <vtkVertex.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkPointData.h>
#include <vtkCellData.h>
#include <vtkIntArray.h>
#include <vtkDoubleArray.h>
#include <vtkGlyph3D.h>
#include <vtkDistanceToCamera.h>
#include <vtkMaskPoints.h>
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkTransform.h>
#include <vtkPolyData.h>
#include <vtkSelectionNode.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkIdTypeArray.h>
#include <vtkUnstructuredGrid.h>

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaAbstractLoad.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaAbstractBoundaryCondition.h"
#include "FITK_Interface/FITKInterfacePhysics/FITKBoundaryLocation.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAbstractAssInstance.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAssInstanceM4.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAssInstanceTR.h"
#include "FITK_Interface/FITKInterfaceModel/FITKModelSet.h"
#include "FITK_Component/FITKAbaqusData/FITKAbaModelInfo.h"

namespace Exchange
{
    FITKGraphObjectLoadBase::FITKGraphObjectLoadBase(AbaqusData::FITKDataCase* caseData, Interface::FITKBoundaryLocation* load, int stepId)
        : FITKGraphObjectFixedSizeBase(caseData, load), m_stepId(stepId)
    {
        if (!load)
        {
            return;
        }

        // Initialize.
        init();

        // Set the layer need to be rendered.
        setRenderLayer(0, 1);

        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = false;
    }

    FITKGraphObjectLoadBase::~FITKGraphObjectLoadBase()
    {
        clearCache();
    }

    void FITKGraphObjectLoadBase::setVisible(bool visibility)
    {
        for (FITKGraphActor* fActor : m_loadActorList)
        {
            if (fActor)
            {
                fActor->SetVisibility(visibility);
            }
        }
    }

    void FITKGraphObjectLoadBase::changeStep(int stepId)
    {
        if (stepId == m_stepId)
        {
            m_stepChanged = false;

            // Update the visibility only.
            updateVisibility();

            return;
        }
        else
        {
            // Save the step id before updating the graph.
            m_stepId = stepId;

            // Change the flag.
            m_stepChanged = true;

            // Get the value first.
            getValue(m_stepId);

            // Update if necessary.
            update();

            // Set the flag to false again after updating.
            m_stepChanged = false;
        }
    }

    void FITKGraphObjectLoadBase::update(bool forceUpdate)
    {
        Interface::FITKBoundaryLocation* load = dynamic_cast<Interface::FITKBoundaryLocation*>(_dataObj);
        if (!m_polyData || !load)
        {
            return;
        }

        // Clear data.( For value or set changed )
        resetVtkObj(m_points);
        m_cells->Delete();
        m_cells = vtkCellArray::New();
        resetVtkObj(m_normal);
        resetVtkObj(m_scalar);

        // Get set detail information.
        AbaqusData::AbaModelInfo requiredData = AbaqusData::FITKAbaModelInfo::GetModelInfo(load);

        // Get the new value of load.
        getValue(m_stepId);

        // Some of the load does not need a set.( Gravity and so on. )
        if (requiredData._set)
        {
            // Get the set id.
            int setId = requiredData._set->getDataObjectID();

            // Clear data.( For set changed )
            if (setId != m_setId || forceUpdate)
            {
                // Clear data.
                clearData();

                bool flag = generatePolyData(load);
                if (!flag)
                {
                    return;
                }
            }
        }
        else
        {
            // The reason of set is nullptr: The set is deleted.
            clearData();
        }

        // Add data again.
        int nPoints = m_loadPoints->GetNumberOfPoints();
        for (int i = 0; i < nPoints; i++)
        {
            double* pos = m_loadPoints->GetPoint(i);
            insertNextPoint(pos, -1, false);
        }

        // Update the visibility.
        updateVisibility();
    }

    void FITKGraphObjectLoadBase::updateVisibility()
    {
        // Update the full actor's visibility.
        FITKGraphObject3D::updateVisibility();

        // Update the extractor's ids.
        if (!m_idArray || !m_modelIdArray)
        {
            return;
        }

        QHash<int, bool> modelVisibilityHash;

        // Save the visibility first, and check if the visbility has changed at the same time.
        bool hasChanged = false;
        for (int modelId : m_modelIds)
        {
            bool parentVisible = getParentDataVisibility(modelId);
            if (modelId == -1)
            {
                parentVisible = true;
            }

            bool oldVisibility = false;
            if (m_modelVisibilityHash.contains(modelId))
            {
                oldVisibility = m_modelVisibilityHash[modelId];
            }

            if (parentVisible != oldVisibility)
            {
                hasChanged = true;
            }

            modelVisibilityHash.insert(modelId, parentVisible);
        }

        // If the visibility hasn't changed, then return.
        if (!hasChanged && !m_modelVisibilityHash.isEmpty())
        {
            return;
        }

        // Reset the extractor's ids.
        resetVtkObj(m_idArray);

        // Update the extractor's ids.
        for (int i = 0; i < m_modelIdArray->GetNumberOfValues(); i++)
        {
            int modelId = m_modelIdArray->GetValue(i);
            if (modelId == -1)
            {
                m_idArray->InsertNextValue(i);
            }
            else
            {
                if (modelVisibilityHash[modelId])
                {
                    m_idArray->InsertNextValue(i);
                }
            }
        }

        // Update the data.
        m_polyData->Modified();

        // Save the flags.
        m_modelVisibilityHash = modelVisibilityHash;
    }

    void FITKGraphObjectLoadBase::simplified(bool enabled)
    {
        if (!m_graphSimplified)
        {
            return;
        }

        bool dataVisible = getDataVisibility();

        // Change the actor visibility while changing the camera.
        if (dataVisible)
        {
            for (FITKGraphActor* fActor : m_loadActorList)
            {
                if (fActor)
                {
                    fActor->SetVisibility(!enabled);
                }
            }
        }
    }

    bool FITKGraphObjectLoadBase::isRelatedTo(int dataObjId)
    {
        // Check if the id is the self-object id.
        bool isTheObj = FITKGraphObject3D::isRelatedTo(dataObjId);
        if (isTheObj)
        {
            return true;
        }

        // Check if the id of surface or set is the same as data id.
        bool isLoc = (m_setId == dataObjId) || (m_surfaceId == dataObjId);
        if (isLoc)
        {
            return true;
        }

        // Check the sub set ids.
        bool isSubSet = (m_subSetIds.contains(dataObjId));
        if (isSubSet)
        {
            return true;
        }

        // Check if the model ids contains the data id.
        return m_modelIds.contains(dataObjId);
    }

    bool FITKGraphObjectLoadBase::getFixedBounds(double* bounds)
    {
        if (!m_hasFixedBounds)
        {
            return false;
        }

        // If the graph object is invisible, then return.
        bool visibile = getDataVisibility();
        if (!visibile)
        {
            return false;
        }

        // Get the component's bounds only.
        if (!m_polyData)
        {
            return false;
        }

        double* bds = m_polyData->GetBounds();
        if (!bds)
        {
            return false;
        }

        for (int i = 0; i < 6; i++)
        {
            bounds[i] = bds[i];
        }

        return true;
    }

    void FITKGraphObjectLoadBase::clearCache()
    {
        // Delete the points.
        deleteVtkObj(m_loadPoints);

        // Delete source data.
        deleteVtkObjs(m_sourceList);

        // Delete poly data.
        deleteVtkObj(m_points);
        deleteVtkObj(m_cells);
        deleteVtkObj(m_polyData);
        deleteVtkObj(m_normal);
        deleteVtkObj(m_scalar);
        deleteVtkObj(m_modelIdArray);

        // Delete extractor pointers.
        deleteVtkObj(m_selectNode);
        deleteVtkObj(m_section);
        deleteVtkObj(m_extractSelection);
        deleteVtkObj(m_idArray);

        // Clear the actor list.
        m_loadActorList.clear();

        // Clear the ids.
        m_subSetIds.clear();
    }

    void FITKGraphObjectLoadBase::getValue(int stepId)
    {
        Q_UNUSED(stepId);
    }

    bool FITKGraphObjectLoadBase::generatePolyData(Interface::FITKBoundaryLocation* load)
    {
        Q_UNUSED(load);
        return false;
    }

    bool FITKGraphObjectLoadBase::getDataVisibility()
    {
        bool isActive = false;

        // Try to down cast to load data.       
        Interface::FITKAbaAbstractLoad* load = dynamic_cast<Interface::FITKAbaAbstractLoad*>(_dataObj);
        if (load)
        {
            isActive = load->getActiveState();
        }

        // Try to down cast to BC data.
        Interface::FITKAbaAbstractBoundaryCondition* bc = dynamic_cast<Interface::FITKAbaAbstractBoundaryCondition*>(_dataObj);
        if (bc)
        {
            isActive = bc->getActiveState();
        }

        return isActive;
    }

    void FITKGraphObjectLoadBase::clearModelsInfo()
    {
        m_modelIds.clear();
        m_modelVisibilityHash.clear();
        m_subSetIds.clear();

        resetVtkObj(m_modelIdArray);
    }

    void FITKGraphObjectLoadBase::clearData()
    {
        // Reset the saved load points.
        resetVtkObj(m_loadPoints);

        // Reset the component id.
        m_setId = -1;
        m_surfaceId = -1;

        // Clear sub set ids.
        m_subSetIds.clear();

        // Clear model information.
        clearModelsInfo();
    }

    bool FITKGraphObjectLoadBase::checkDataChanged()
    {
        // Override in sub-class if neccessary.
        return false;
    }

    void FITKGraphObjectLoadBase::init()
    {
        // Create a point list for refresh load glyph.
        m_loadPoints = vtkPoints::New();

        // Create poly data.
        //@{
        m_polyData = vtkPolyData::New();
        m_points = vtkPoints::New();
        m_cells = vtkCellArray::New();
        m_normal = vtkDoubleArray::New();
        m_scalar = vtkDoubleArray::New();
        m_modelIdArray = vtkIntArray::New();

        m_normal->SetNumberOfComponents(3);
        m_scalar->SetNumberOfComponents(1);
        m_modelIdArray->SetNumberOfComponents(1);
        //@}

        // Initialize the extractor.
        //@{
        m_selectNode = vtkSelectionNode::New();
        m_section = vtkSelection::New();
        m_extractSelection = vtkExtractSelection::New();
        m_selectNode->SetContentType(vtkSelectionNode::INDICES);
        m_section->AddNode(m_selectNode);
        m_extractSelection->SetInputData(1, m_section);

        m_idArray = vtkIdTypeArray::New();
        m_selectNode->SetSelectionList(m_idArray);
        //@}
    }

    void FITKGraphObjectLoadBase::highlight(QColor color)
    {
        // If the object is invisible, then return.
        if (!getDataVisibility())
        {
            return;
        }

        // Get the load highlight color.
        double color3[3] = { 0 ,0, 0 };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_loadHighlightColor, color3);
        }
        

        // Set the highlight color.
        for (FITKGraphActor* fActor : m_loadActorList)
        {
            if (fActor)
            {
                fActor->GetProperty()->SetColor(color3);
            }
        }

        FITKGraphObject3D::highlight();
    }

    void FITKGraphObjectLoadBase::disHighlight()
    {
        // Set the default color.
        for (FITKGraphActor* fActor : m_loadActorList)
        {
            if (fActor)
            {
                initActorProperties(fActor, m_details);
            }
        }

        FITKGraphObject3D::disHighlight();
    }

    void FITKGraphObjectLoadBase::generateGraph(vtkPolyDataAlgorithm* src, double size, QVariant details)
    {
        Q_UNUSED(details);

        if (!m_polyData || !m_normal || !m_scalar || !m_points || !m_cells || !src)
        {
            return;
        }

        // Set the normals and scalars.
        m_polyData->GetPointData()->SetNormals(m_normal);
        m_polyData->GetPointData()->SetScalars(m_scalar);

        m_polyData->SetPoints(m_points);
        m_polyData->SetVerts(m_cells);

        // Glyph.
        vtkGlyph3D* glyph = vtkGlyph3D::New();
        vtkDistanceToCamera* distanceToCamera = vtkDistanceToCamera::New();

        if (size <= 0)
        {
            distanceToCamera->SetScreenSize(m_glyphSize);
        }
        else
        {
            distanceToCamera->SetScreenSize(size);
        }
        
        glyph->SetInputConnection(distanceToCamera->GetOutputPort());

        // Add the point array name need to be used.      
#if VTK_MAJOR_VERSION < 8
        glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
#else
        glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distanceToCamera->GetDistanceArrayName());
#endif

        glyph->SetScaling(true);
        glyph->SetScaleModeToScaleByScalar();
        glyph->SetVectorModeToUseNormal();

        // Set the soure data and input data.
        if (m_useMaskPoints)
        {
            // Initialize the mask points.
            vtkMaskPoints* maskPoints = vtkMaskPoints::New();
            FITKGraphCommons::InitializeMaskPoints(maskPoints, m_extractSelection->GetOutputPort(), m_maxSampleNumber);
            m_maskPointsList.push_back(maskPoints);

            distanceToCamera->SetInputConnection(maskPoints->GetOutputPort());
        }
        else
        {
            distanceToCamera->SetInputConnection(m_extractSelection->GetOutputPort());
        }

        glyph->SetSourceConnection(src->GetOutputPort());
        m_extractSelection->SetInputData(m_polyData);

        // Add to data list.
        m_glyphList.push_back(glyph);
        m_distToCameraList.push_back(distanceToCamera);

        // Create actor.
        FITKGraphActor* actor = FITKGraphActor::New();
        actor->setGraphObject(this);
        actor->SetPickable(false);
        actor->setInputConnection(glyph->GetOutputPort());
        addActor(actor);
        m_loadActorList.push_back(actor);

        // Initialize actor's properties.( Virtual function )
        initActorProperties(actor, m_details);
    }

    void FITKGraphObjectLoadBase::insertNextPoint(double* pos, int modelId, bool addToLoadPoints)
    {
        Q_UNUSED(pos);
        Q_UNUSED(modelId);
        Q_UNUSED(addToLoadPoints);
    }
}   // namespace Exchange